package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import nachos.vm.VMProcess;

import java.io.EOFException;



/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see	nachos.vm.VMProcess
 * @see	nachos.network.NetProcess
 */
public class UserProcess {
    /**
     * Allocate a new process.
     */
    public UserProcess() {
	int numPhysPages = Machine.processor().getNumPhysPages();
	pageTable = new TranslationEntry[numPhysPages];
	currentPageTableSize = 0;
	
	fdManager = new FDManager();
//	for (int i=0; i<numPhysPages; i++)
//	    pageTable[i] = new TranslationEntry(i,i, true,false,false,false);
    }
    
    /**
     * Allocate and return a new process of the correct class. The class name
     * is specified by the <tt>nachos.conf</tt> key
     * <tt>Kernel.processClassName</tt>.
     *
     * @return	a new process of the correct class.
     */
    public static UserProcess newUserProcess() {
	return (UserProcess)Lib.constructObject(Machine.getProcessClassName());
    }

    /**
     * Execute the specified program with the specified arguments. Attempts to
     * load the program, and then forks a thread to run it.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the program was successfully executed.
     */
    public boolean execute(String name, String[] args) {
    
    this.name = name;
    pid = PIDManager.allocatePID(this);
        	
	if (!load(name, args))
	    return false;

	// now all mem needed by this process has been allocated
//	UserKernel.printPhysPageStatus();
	
	thread = (UThread) new UThread(this).setName(name);
	thread.fork();
	isRunning = true;
	return true;
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * Called by <tt>UThread.saveState()</tt>.
     */
    public void saveState() {
    }

    /**
     * Restore the state of this process after a context switch. Called by
     * <tt>UThread.restoreState()</tt>.
     */
    public void restoreState() {
	Machine.processor().setPageTable(pageTable);
    }

    /**
     * Read a null-terminated string from this process's virtual memory. Read
     * at most <tt>maxLength + 1</tt> bytes from the specified address, search
     * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
     * without including the null terminator. If no null terminator is found,
     * returns <tt>null</tt>.
     *
     * @param	vaddr	the starting virtual address of the null-terminated
     *			string.
     * @param	maxLength	the maximum number of characters in the string,
     *				not including the null terminator.
     * @return	the string read, or <tt>null</tt> if no null terminator was
     *		found.
     */
    public String readVirtualMemoryString(int vaddr, int maxLength) {
	Lib.assertTrue(maxLength >= 0);

	byte[] bytes = new byte[maxLength+1];

	int bytesRead = readVirtualMemory(vaddr, bytes);

	for (int length=0; length<bytesRead; length++) {
	    if (bytes[length] == 0)
		return new String(bytes, 0, length);
	}

	return null;
    }

    /**
     * Transfer data from this process's virtual memory to all of the specified
     * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data) {
	return readVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from this process's virtual memory to the specified array.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @param	offset	the first byte to write in the array.
     * @param	length	the number of bytes to transfer from virtual memory to
     *			the array.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data, int offset,
				 int length) {
	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

	byte[] memory = Machine.processor().getMemory();
		
	// use translation
	
	int totalRead = 0;
	
	int vaddrOffset = Processor.offsetFromAddress(vaddr);
	int haddr = translate(vaddr, false);
	if (haddr == -1) {
		return totalRead;
	}
	int vpn = Processor.pageFromAddress(vaddr);
	
	if (length <= pageSize - vaddrOffset) {
		// simple case, just need to read one page
		// handle first page, may not begin read from offset 0 in page
		pinPage(vpn);
		// since haddr may change due to pinPage
		if (this instanceof VMProcess) haddr = translate(vaddr, false); 
		System.arraycopy(memory, haddr, data, offset, length);
		unPinPage(vpn, false);
		totalRead = length;
		return totalRead;
	}
	else {
		// involve multiple pages read
		
		// handle first page, may not begin read from offset 0 in page
		int lengthRemain = length;
		int currentDataOffset = offset;
		int currentHaddr = haddr;
		int currentVaddr = vaddr;
		int currentLength = pageSize - vaddrOffset;
		pinPage(vpn);
		// since haddr may change due to pinPage
		if (this instanceof VMProcess) currentHaddr = translate(currentVaddr, false); 
		System.arraycopy(memory, currentHaddr, data, currentDataOffset, currentLength);
		unPinPage(vpn, false);
		totalRead += currentLength;
		currentVaddr += currentLength;
		currentDataOffset += currentLength;
		lengthRemain -= currentLength;
		// handle rest of the pages, must begin read from offset 0 in page
		while (lengthRemain > 0) {
			if(lengthRemain <= pageSize) {
				currentLength = lengthRemain;  // last page
			} else {
				currentLength = pageSize;
			}
			currentHaddr = translate(currentVaddr, false);
			if (currentHaddr == -1) {
				return totalRead;
			}
			int currentVpn = Processor.pageFromAddress(currentVaddr);
			pinPage(currentVpn);
			// since haddr may change due to pinPage
			if (this instanceof VMProcess) currentHaddr = translate(currentVaddr, false); 
			System.arraycopy(memory, currentHaddr, data, currentDataOffset, currentLength);
			unPinPage(currentVpn, false);
			totalRead += currentLength;
			currentVaddr += currentLength;
			currentDataOffset += currentLength;
			lengthRemain -= currentLength;
		}
		return totalRead;
	}
	}
    
    /**
     * 
     * @param vaddr
     * @param isWrite
     * @return the paddr, -1 if write to read only page, or no pageTable entry for the vaddr 
     */
    protected int translate(int vaddr, boolean isWrite) {
    	int vpn = Processor.pageFromAddress(vaddr);
    	// check if vpn is in the page table
    	TranslationEntry entry = null;
    	for(int i = 0 ; i < currentPageTableSize ; i++) {
    		if (pageTable[i].vpn == vpn) {
    			entry = pageTable[i];
    		}
    	}
    	if (entry == null) {
    		return -1;
    	}
    	if (entry.readOnly && isWrite) {
    		return -1;
    	}
    	int ppn = entry.ppn;
    	int offset = Processor.offsetFromAddress(vaddr);
    	return Processor.makeAddress(ppn, offset);
    }
    
    /**
     * if the page is not currently in main mem, initialize it or get it from swap
     * @param vpn should be valid
     */
    protected void pinPage(int vpn) {
    	// do nothing here, allow for VMProcess to override
    }
    
    /**
     * also set used/dirty bits properly in page table and sync with TLB if possible
     * @param vpn should be valid
     * @param isWrite if true, set dirty bit in entry
     */
    protected void unPinPage(int vpn, boolean isWrite) {
    	// do nothing here, allow for VMProcess to override    	
    }

    /**
     * Transfer all data from the specified array to this process's virtual
     * memory.
     * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data) {
	return writeVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from the specified array to this process's virtual memory.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @param	offset	the first byte to transfer from the array.
     * @param	length	the number of bytes to transfer from the array to
     *			virtual memory.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data, int offset,
				  int length) {
	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

	byte[] memory = Machine.processor().getMemory();

	// use translation
	
	int totalWrite = 0;
	
	int vaddrOffset = Processor.offsetFromAddress(vaddr);
	int haddr = translate(vaddr, true);
	if (haddr == -1) {
		return totalWrite;
	}
	int vpn = Processor.pageFromAddress(vaddr);
	if (length <= pageSize - vaddrOffset) {
		// simple case, just need to write one page
		// handle first page, may not begin write from offset 0 in page
		pinPage(vpn);
		// since haddr may change due to pinPage
		if (this instanceof VMProcess) haddr = translate(vaddr, true); 
		System.arraycopy(data, offset, memory, haddr, length);
		unPinPage(vpn, true);
		totalWrite = length;
		return totalWrite;
	}
	else {
		// involve multiple pages write
		
		// handle first page, may not begin write from offset 0 in page
		int lengthRemain = length;
		int currentDataOffset = offset;
		int currentHaddr = haddr;
		int currentVaddr = vaddr;
		int currentLength = pageSize - vaddrOffset;
		pinPage(vpn);
		// since haddr may change due to pinPage
		if (this instanceof VMProcess) currentHaddr = translate(currentVaddr, true);
		System.arraycopy(data, currentDataOffset, memory, currentHaddr, currentLength);
		unPinPage(vpn, true);
		totalWrite += currentLength;
		currentVaddr += currentLength;
		currentDataOffset += currentLength;
		lengthRemain -= currentLength;
		// handle rest of the pages, must begin write from offset 0 in page
		while (lengthRemain > 0) {
			if(lengthRemain <= pageSize) {
				currentLength = lengthRemain;  // last page
			} else {
				currentLength = pageSize;
			}
			currentHaddr = translate(currentVaddr, true);
			if (currentHaddr == -1) {
				return totalWrite;
			}
			int currentVpn = Processor.pageFromAddress(currentVaddr);
			pinPage(currentVpn);
			// since haddr may change due to pinPage
			if (this instanceof VMProcess) currentHaddr = translate(currentVaddr, true);
			System.arraycopy(data, currentDataOffset, memory, currentHaddr, currentLength);
			unPinPage(currentVpn, true);
			totalWrite += currentLength;
			currentVaddr += currentLength;
			currentDataOffset += currentLength;
			lengthRemain -= currentLength;
		}
		return totalWrite;
	}
    }

    /**
     * Load the executable with the specified name into this process, and
     * prepare to pass it the specified arguments. Opens the executable, reads
     * its header information, and copies sections and arguments into this
     * process's virtual memory.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the executable was successfully loaded.
     */
    private boolean load(String name, String[] args) {
	Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");
	
	OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
	if (executable == null) {
	    Lib.debug(dbgProcess, "\topen failed");
	    return false;
	}

	try {
	    coff = new Coff(executable);
	}
	catch (EOFException e) {
	    executable.close();
	    Lib.debug(dbgProcess, "\tcoff load failed");
	    return false;
	}

	// make sure the sections are contiguous and start at page 0
	numPages = 0;
	for (int s=0; s<coff.getNumSections(); s++) {
	    CoffSection section = coff.getSection(s);
	    if (section.getFirstVPN() != numPages) {
		coff.close();
		Lib.debug(dbgProcess, "\tfragmented executable");
		return false;
	    }
	    numPages += section.getLength();
	}
	
	coffPageNum = numPages;
	
	// make sure the argv array will fit in one page
	byte[][] argv = new byte[args.length][];
	int argsSize = 0;
	for (int i=0; i<args.length; i++) {
	    argv[i] = args[i].getBytes();
	    // 4 bytes for argv[] pointer; then string plus one for null byte
	    argsSize += 4 + argv[i].length + 1;
	}
	if (argsSize > pageSize) {
	    coff.close();
	    Lib.debug(dbgProcess, "\targuments too long");
	    return false;
	}

	// program counter initially points at the program entry point
	initialPC = coff.getEntryPoint();	

	// next comes the stack; stack pointer initially points to top of it
	numPages += stackPages;
	initialSP = numPages*pageSize;
	

	// and finally reserve 1 page for arguments
	numPages++;

	if (!loadSections())
	    return false;

	if (!loadStackAndArg())
		return false;
	
	this.argc = args.length;
	this.argv = (numPages-1)*pageSize;
	storeArg(args, argv);

	return true;
    }
    
    protected void storeArg(String[] args, byte[][] argv) {
    	// store arguments in last page
    	int entryOffset = (numPages-1)*pageSize;
    	int stringOffset = entryOffset + args.length*4;

//    	this.argc = args.length;
//    	this.argv = entryOffset;
    	
    	for (int i=0; i<argv.length; i++) {
    	    byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
    	    Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4);
    	    entryOffset += 4;
    	    Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) ==
    		       argv[i].length);
    	    stringOffset += argv[i].length;
    	    Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1);
    	    stringOffset += 1;
    	}
    }

    protected boolean loadStackAndArg() {
    	// allocate pmem for stack
    	for (int i = coffPageNum ; i < coffPageNum + stackPages ; i++) {
    		int vpn = i;
    		int ppn = UserKernel.allocatePhysPage(this);
    		if (ppn == -1) {
    			Lib.debug(dbgProcess, "\t *** no more physicial page!");
    	    	return false;
    		}
    		pageTable[currentPageTableSize++] = 
    	    	new TranslationEntry(vpn, ppn, true,false,false,false);
    	}
    	
    	// allocate pmem for arguments
    	int vpn = numPages - 1;
    	int ppn = UserKernel.allocatePhysPage(this);
    	if (ppn == -1) {
    		Lib.debug(dbgProcess, "\t *** no more physicial page!");
        	return false;
    	}
    	pageTable[currentPageTableSize++] = 
        	new TranslationEntry(vpn, ppn, true,false,false,false);
    	
    	return true;
    }
    
    /**
     * Allocates memory for this process, and loads the COFF sections into
     * memory. If this returns successfully, the process will definitely be
     * run (this is the last step in process initialization that can fail).
     *
     * @return	<tt>true</tt> if the sections were successfully loaded.
     */
    protected boolean loadSections() {
	if (numPages > Machine.processor().getNumPhysPages()) {
	    coff.close();
	    Lib.debug(dbgProcess, "\tinsufficient physical memory");
	    return false;
	}

	// load sections
	for (int s=0; s<coff.getNumSections(); s++) {
	    CoffSection section = coff.getSection(s);
	    
	    Lib.debug(dbgProcess, "\tinitializing " + section.getName()
		      + " section (" + section.getLength() + " pages)");

	    for (int i=0; i<section.getLength(); i++) {
		
		// allocate physical page for sections
	    int vpn = section.getFirstVPN()+i;
	    int ppn = UserKernel.allocatePhysPage(this);
	    if (ppn == -1) {
	    	Lib.debug(dbgProcess, "\t *** no more physicial page!");
	    	return false;
	    }
	    pageTable[currentPageTableSize++] = 
	    	new TranslationEntry(vpn, ppn, true,section.isReadOnly(),false,false);
		section.loadPage(i, ppn);
	    }
	}
	
	return true;
    }

    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    protected void unloadSections() {
    }    

    /**
     * Initialize the processor's registers in preparation for running the
     * program loaded into this process. Set the PC register to point at the
     * start function, set the stack pointer register to point at the top of
     * the stack, set the A0 and A1 registers to argc and argv, respectively,
     * and initialize all other registers to 0.
     */
    public void initRegisters() {
	Processor processor = Machine.processor();

	// by default, everything's 0
	for (int i=0; i<processor.numUserRegisters; i++)
	    processor.writeRegister(i, 0);

	// initialize PC and SP according
	processor.writeRegister(Processor.regPC, initialPC);
	processor.writeRegister(Processor.regSP, initialSP);

	// initialize the first two argument registers to argc and argv
	processor.writeRegister(Processor.regA0, argc);
	processor.writeRegister(Processor.regA1, argv);
    }

    /**
     * Handle the halt() system call. 
     */
    private int handleHalt() {
    
    if (this.pid != 1) {
    	return 0; // not the root process
    }
	Machine.halt();
	
	Lib.assertNotReached("Machine.halt() did not halt machine!");
	return 0;
    }




    /**
     * Handle a syscall exception. Called by <tt>handleException()</tt>. The
     * <i>syscall</i> argument identifies which syscall the user executed:
     *
     * <table>
     * <tr><td>syscall#</td><td>syscall prototype</td></tr>
     * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
     * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
     * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
     * 								</tt></td></tr>
     * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
     * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
     * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
     * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
     * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
     * </table>
     * 
     * @param	syscall	the syscall number.
     * @param	a0	the first syscall argument.
     * @param	a1	the second syscall argument.
     * @param	a2	the third syscall argument.
     * @param	a3	the fourth syscall argument.
     * @return	the value to be returned to the user.
     */
    public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
	switch (syscall) {
	case syscallHalt:
	    return handleHalt();
	case syscallExit:
		return handleExit(a0);
	case syscallCreate:
		return handleCreat(a0);
	case syscallOpen:
		return handleOpen(a0);
	case syscallRead:
		return handleRead(a0, a1, a2);
	case syscallWrite:
		return handleWrite(a0, a1, a2);
	case syscallClose:
		return handleClose(a0);
	case syscallUnlink:
		return handleUnlink(a0);
	case syscallExec:
		return handleExec(a0, a1, a2);
	case syscallJoin:
		return handleJoin(a0, a1);
	default:
	    Lib.debug(dbgProcess, "Unknown syscall " + syscall);
	    this.handleExit(EXIT_ABNORMALLY);
	    Lib.assertNotReached("Unknown system call!");
	}
	return 0;
    }
    
    /**
     * handle int  creat(char *name); syscall
     * @return
     */
    private int handleCreat(int nameAddr) {
    	String name = readVirtualMemoryString(nameAddr, STRING_ARG_MAX_LENGTH);
    	if (null == name || "".equals(name)) {
    		return -1;
    	}
//    	OpenFile openFile = ThreadedKernel.fileSystem.open(name, false);  // test if the file exist
//    	if (openFile != null) {
//    		// file already exit
//    		return fdManager.allocate(openFile); // return descriptor
//    	}
    	// No matter the file exist or not, create it
    	OpenFile openFile = ThreadedKernel.fileSystem.open(name, true);
    	if (null == openFile) {
    		return -1; // create fail
    	}
    	// if no fd is available will return -1
    	int fileDescriptor = fdManager.allocate(openFile); 
    	return fileDescriptor;
    }
    
    private int handleOpen(int nameAddr) {
    	String name = readVirtualMemoryString(nameAddr, STRING_ARG_MAX_LENGTH);
    	if (null == name || "".equals(name)) {
    		return -1;
    	}
    	OpenFile openFile = ThreadedKernel.fileSystem.open(name, false);
    	if (null == openFile) {
    		return -1;
    	}
    	int fileDescriptor = fdManager.allocate(openFile); 
    	return fileDescriptor;
    }
    
    private int handleRead(int fileDescriptor, int destBufferAddr, int count) {
    	if (count < 0) {
    		return -1;
    	}
//    	if (fileDescriptor == STDIN) { //  
//    		OpenFile stdin = UserKernel.console.openForReading();
//    		byte[] data = new byte[count];
//    		int readCount = stdin.read(data, 0, count);
//    		if (readCount == count) {
//    			return writeVirtualMemory(destBufferAddr, data);
//    		} else {
//    			return -1;
//    		}
//    	}
    	if (!fdManager.checkValid(fileDescriptor)) {
    		return -1;
    	}
    	if (translate(destBufferAddr, true) == -1) {
    		return -1;
    	}
    	// read data from file or stdin/stdout, assume count is big
    	OpenFile openFile = fdManager.getOpenFile(fileDescriptor);
//    	int positionInFile = fdManager.getPositionInFile(fileDescriptor);
    	int totalBytesWriten = 0;
    	int currentBytesWriten = 0;
    	int currentBytesRead = 0;
    	int currentBytesToRead = 0;
    	int countRemain = count;
    	byte[] tempBuffer = new byte[TEMP_BUFFER_SIZE];
    	while (countRemain > 0) {
    		if (countRemain > TEMP_BUFFER_SIZE) 
    			currentBytesToRead = TEMP_BUFFER_SIZE;
    		else
    			currentBytesToRead = countRemain;
    		currentBytesRead = openFile.read(tempBuffer, 0, currentBytesToRead);
//    		if (currentBytesRead == -1) return -1; // ignore...
    		
//    		positionInFile += currentBytesRead;
    		currentBytesWriten = 
    			writeVirtualMemory(destBufferAddr, tempBuffer, 0, currentBytesRead);

    		destBufferAddr += currentBytesWriten;
    		totalBytesWriten += currentBytesWriten;
    		if (currentBytesWriten < currentBytesToRead) break; // if reach end of file or reach invalid mem
    		countRemain -= currentBytesWriten; // assume currentBytesWriten == currentBytesRead
    	}
//    	fdManager.setPositionInFile(fileDescriptor, positionInFile);
    	return totalBytesWriten;
    }
    
    private int handleWrite(int fileDescriptor, int sourceBufferAddr, int count) {
    	if (count < 0) {
    		return -1;
    	}
//    	if (fileDescriptor == STDOUT) {
//    		OpenFile stdout = UserKernel.console.openForWriting();
//    		byte[] data = new byte[count];
//    		int readCount = readVirtualMemory(sourceBufferAddr, data, 0, count);
//    		if (readCount == count) {
//    			return stdout.write(data, 0, count);
//    			
//    		} else {
//    			return -1;
//    		}
//    		
//    	}
    	if (!fdManager.checkValid(fileDescriptor)) {
    		return -1;
    	}
    	if (translate(sourceBufferAddr, false) == -1) {
    		return -1;
    	}
    	
    	// write data from file, assume count is big
    	OpenFile openFile = fdManager.getOpenFile(fileDescriptor);
//    	int positionInFile = fdManager.getPositionInFile(fileDescriptor);
    	int totalBytesWriten = 0;
    	int currentBytesWriten = 0;
    	int currentBytesRead = 0;
    	int currentBytesToRead = 0;
    	int countRemain = count;
    	byte[] tempBuffer = new byte[TEMP_BUFFER_SIZE];
    	while (countRemain > 0) {
    		if (countRemain > TEMP_BUFFER_SIZE) 
    			currentBytesToRead = TEMP_BUFFER_SIZE;
    		else
    			currentBytesToRead = countRemain;
    		currentBytesRead = readVirtualMemory(sourceBufferAddr, tempBuffer, 0, currentBytesToRead);
    		sourceBufferAddr += currentBytesRead;

    		currentBytesWriten = openFile.write(tempBuffer, 0, currentBytesRead);
    		if (currentBytesWriten == -1) return totalBytesWriten; // write error
    		
    		// if (currentBytesWriten < currentBytesRead) return -1; // disk full, return -1
    		
//    		positionInFile += currentBytesWriten;
    		totalBytesWriten += currentBytesWriten;
    		if (currentBytesWriten < currentBytesToRead) break; // if reach invalid mem or disk full
    		countRemain -= currentBytesWriten;
    	}
//    	fdManager.setPositionInFile(fileDescriptor, positionInFile);
    	return totalBytesWriten;
    	
    }
    
    private int handleClose(int fd) {
    	if (!fdManager.checkValid(fd)) {
    		return -1;
    	}
    	OpenFile openFile = fdManager.getOpenFile(fd);
    	openFile.close();
    	fdManager.free(fd);
    	return 0;
    }
    
    private int handleUnlink(int nameAddr) {
    	String name = readVirtualMemoryString(nameAddr, STRING_ARG_MAX_LENGTH);
    	if (null == name || "".equals(name)) {
    		return -1;
    	}
    	return ThreadedKernel.fileSystem.remove(name) ? 0 : -1;
    }
    
    private int handleExec(int fileNameAddr, int argc, int argvAddr) {
    	String fileName = readVirtualMemoryString(fileNameAddr, STRING_ARG_MAX_LENGTH);
    	if (null == fileName || "".equals(fileName)) {
    		return -1;
    	}
    	if (!fileName.endsWith(".coff")) {
    		return -1;
    	}
    	if (argc < 0) {
    		return -1;
    	}
    	if (argc > 0 && translate(argvAddr, false) == -1) {
    		return -1;
    	}
    	// parse args
    	String[] args = new String[argc];
    	for (int i = 0 ; i < argc ; i++) {
    		byte[] data = new byte[4];
    		int readNum = readVirtualMemory(argvAddr, data, 0, 4);
    		if (readNum != 4) // if addr of argString addr is invalid
    			return -1;
    		argvAddr += 4;
    		int argStringAddr = makeAddr(data);
    		String argString = readVirtualMemoryString(argStringAddr, STRING_ARG_MAX_LENGTH);
    		if (null == argString || "".equals(argString)) // if argString addr is invalid
    			return -1;
    		args[i] = argString;
    	}
    	UserProcess newProcess = UserProcess.newUserProcess();
    	Lib.debug(dbgProcess, "\t---try to exec:" + fileName);
    	boolean executeResult = newProcess.execute(fileName, args);
    	if (!executeResult) {
    		// free allocated resources
    		newProcess.isExitNormally = false;
    		newProcess.handleExit(EXIT_ABNORMALLY);
    		return -1;
    	}
    	newProcess.parentPID = this.pid;
    	return newProcess.pid;
    }
    
    private int handleJoin(int pid, int statusAddr) {
    	UserProcess childProcess = PIDManager.getUserProcess(pid);
    	if (childProcess == null) {
    		return -1; // pid not exist
    	}
    	if (childProcess.parentPID != this.pid) {
    		return -1;  // parent doesn't own this child
    	}
    	if (translate(statusAddr, true) == -1) {
    		return -1;  // if status addr is invalid or read-only
    	}
    	if (childProcess.exitCode != null) {
    		 // child process has exited already
    		childProcess.parentPID = -1; // disown the child process
    		byte[] statusBytes = Lib.bytesFromInt(childProcess.exitCode);
        	writeVirtualMemory(statusAddr, statusBytes);
        	if (!childProcess.isExitNormally) {
        		return 0; // child exit abnormally
        	} else {
        		return 1;
        	}
    	}
    	childProcess.thread.join(); // go to sleep here
    	// wake up by child here, child has exited
    	childProcess.parentPID = -1; // disown the child process
    	// write status
    	byte[] statusBytes = Lib.bytesFromInt(childProcess.exitCode);
    	writeVirtualMemory(statusAddr, statusBytes);
    	if (!childProcess.isExitNormally) {
    		return 0; // child exit abnormally
    	} else {
    		return 1;
    	}
    }
    
    protected int handleExit(int exitCode) {
    	this.exitCode = exitCode;
//    	if (this.joinedThread != null) {
//    		// wake up parent thread(process)
//    		Machine.interrupt().disable();
//    		this.joinedThread.ready();
//    		this.joinedThread = null;
//    	}
    	cleanup();
    	if (isExitNormally) {
    		Lib.debug(dbgProcess, "exit normally");
    	} else {
    		Lib.debug(dbgProcess, "exit abnormally");
    	}
    	isRunning = false;
    	// check if it is the last process
    	if (!PIDManager.hasProcessRunning()) {
    		Kernel.kernel.terminate(); // never return
    	}
    	
    	if (this.thread != null) {
    		this.thread.finish(); // nothing can run after this instruction!!!
    	}
    	
    	return 0; // never return!!
    }
    
    private void cleanup() {
    	this.freeAllMemory();
    	if (null!=this.fdManager) {
    		this.fdManager.closeAllOpenFiles();
    	}
//    	if (null!=this.executable) {
//    		executable.close();
//    	}
    	if (null!=this.coff) {
    		coff.close();
    	}
    	Lib.debug('a', "pid:"+pid+"'s resource freed");
    }
    
    /**
     * use 4 byte to get a 32-bit addr
     * @param bytes first byte goes to lowest bits
     * @return the 32-bit addr
     */
    private int makeAddr(byte[] bytes) {
    	int result = 0x0000;
    	for (int i = 3 ; i >= 0 ; i--) {
    		result |= ((int)bytes[i] & 0xff);
    		if (i != 0) {
    			result = result << 8;
    		}
    	}
    	return result;
    }
    
    protected void freeAllMemory() {
    	if (pageTable == null) {
    		return;
    	}
    	for (int i = 0 ; i < currentPageTableSize ; i++) {
    		int ppn = pageTable[i].ppn;
    		UserKernel.deallocatePhysPage(ppn, this);
    	}
    }
    
    
    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    public void handleException(int cause) {
	Processor processor = Machine.processor();

	switch (cause) {
	case Processor.exceptionSyscall:
	    int result = handleSyscall(processor.readRegister(Processor.regV0),
				       processor.readRegister(Processor.regA0),
				       processor.readRegister(Processor.regA1),
				       processor.readRegister(Processor.regA2),
				       processor.readRegister(Processor.regA3)
				       );
	    processor.writeRegister(Processor.regV0, result);
	    processor.advancePC();
	    break;				       
				       
	default:
	    Lib.debug(dbgProcess, "Unexpected exception: " +
		      Processor.exceptionNames[cause]);
	    this.isExitNormally = false;
	    this.handleExit(EXIT_ABNORMALLY);
	    Lib.assertNotReached("Unexpected exception");
	}
    }
    
    
    
    private static final int STRING_ARG_MAX_LENGTH = 256;
    private static final int STDIN = 0;
    private static final int STDOUT = 1;
    private static final int TEMP_BUFFER_SIZE = 2048;
    public static final int EXIT_ABNORMALLY = 1;

    /** The program being run by this process. */
    protected Coff coff;

    /** This process's page table. */
    protected TranslationEntry[] pageTable; 
    private int currentPageTableSize;
    /** The number of contiguous pages occupied by the program. */
    protected int numPages;

    /** The number of pages in the program's stack. */
    protected final int stackPages = 8;
    
    private int initialPC, initialSP;
    private int argc, argv;
	
    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';
    
    private static final int
    syscallHalt = 0,
	syscallExit = 1,
	syscallExec = 2,
	syscallJoin = 3,
	syscallCreate = 4,
	syscallOpen = 5,
	syscallRead = 6,
	syscallWrite = 7,
	syscallClose = 8,
	syscallUnlink = 9;
    
    
    /**
     * coff file name
     */
    public String name;
    /**
     * root process's pid is 1
     */
    public int pid;
    /**
     * -1 indicates no parent
     */
    public int parentPID = -1; 
    /**
     * file descriptor manager
     */
    private FDManager fdManager;
    /**
     * thread that belongs to this process
     */
    public UThread thread;
    /**
     * a thread that join to this thread, null indicates no such thread.
     */
//    public UThread joinedThread = null;
    /**
     * null indicates this process has not exited
     */
    public Integer exitCode = null;
    public boolean isExitNormally = true; // default, true
    
    public boolean isRunning = false;
    
    /**
     * used for VM process, demand paging
     */
    protected int coffPageNum;
    
    public String toString() {
    	return name+" "+pid;
    }

}

/**
 * Does include stdin and stdout
 * @author gen
 *
 */
class FDManager {
	private static final int SIZE = 16; // coff counts?
	
	// the index to these array is the fd
	private OpenFile[] openFiles = new OpenFile[SIZE];
//	private int[] positionInFile = new int[SIZE];
	
	public FDManager() {
		// open stdin and stdout and assign fd 0 and 1 to them
		OpenFile stdin = UserKernel.console.openForReading();
		OpenFile stdout= UserKernel.console.openForWriting();
		openFiles[0] = stdin;
		openFiles[1] = stdout;
	}
	
	/**
	 * Allocate a file descriptor for the given openFile.
	 * @param openFile
	 * @return the allocated file descriptor, if no file descriptor available return -1
	 */
	public int allocate(OpenFile openFile) {
		for (int i = 0 ; i < SIZE ; i++) {
			if (null == openFiles[i]) {
				openFiles[i] = openFile;
//				positionInFile[i] = 0; 
				return i;
			}
		}
		// no file descriptor available return -1
		return -1;
	}
	
	/**
	 * 0 and 1 is valid
	 * @param fileDescriptor
	 * @return if fileDescriptor is out of range or corresponding openFile is null, return false.
	 * 			otherwise, return true.
	 */
	public boolean checkValid(int fileDescriptor) {
		if (fileDescriptor < 0 || fileDescriptor >= SIZE) {
			return false;
		}
		if (openFiles[fileDescriptor] == null) {
			return false;
		}
		return true;
	}
	
	/**
	 * should call check first
	 * @param fileDescriptor
	 * @return
	 */
	public OpenFile getOpenFile(int fileDescriptor) {
		return openFiles[fileDescriptor];
	}
	
//	public int getPositionInFile(int fd) {
//		return positionInFile[fd];
//	}
	
//	public void setPositionInFile(int fd, int position) {
//		positionInFile[fd] = position;
//	}
	
	/**
	 * should call check first
	 * @param fileDescriptor
	 */
	public void free(int fileDescriptor) {
		openFiles[fileDescriptor] = null;
//		positionInFile[fileDescriptor] = 0;
	}
	
	public void closeAllOpenFiles() {
		for (int i = 0 ; i < SIZE ; i++) {
			if (null != openFiles[i]) {
				openFiles[i].close();
			}
		}
	}
}
